package listeners

import (
	"blockchains-aio/conf"
	"blockchains-aio/db"
	"blockchains-aio/stack"
	"blockchains-aio/util"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"strconv"
)

//var hardCodedPath = "/data/eth-poa"

func RegisterStack(router *gin.Engine) {
	router.MaxMultipartMemory = 8 << 10 // 8 KB
	router.POST("/stack", doDockStackDeployForEth)
	//router.POST("/me", printMe)
	router.POST("/stack/mixin", doBootstrapMixin)
	router.POST("/stack/mixin/up", doUpdateMixin)

	router.POST("/stack/poseth", doDockStackDeployForPosEth)
	router.GET("/stack/poseth/log", doReadLog)

	router.POST("/stack/tmpl", doGenStackConfig)
	router.POST("/stack/tmpl/pos", doGenStackConfigForPos)
	router.POST("/stack/tmpl/dag", doGenStackConfigForDag)

	router.GET("/stack", doPaginatedStacks)
	router.GET("/stack/class", doClassedPaginatedStacks)
	router.DELETE("/stack/:id", deleteStack)
}

// ls /home/ginvip/blockchainsAioProj/data/nfs/pos/node1/logs
// beacon.log  geth.log  supervisord.log  validator.log
func doReadLog(ctx *gin.Context) {
	strOffset := ctx.Query("offset")
	strLine := ctx.Query("line")
	relativePathToNfs := ctx.Query("relativePathToNfs") // pos/node{0...n}/logs/beacon.log|geth.log|validator.log

	log.Println("received data:")
	log.Printf("strOffset:%s, strLine:%s, relativePathToNfs:%s\n", strOffset, strLine, relativePathToNfs)

	nfs := conf.GetNfsFolder()
	logFilePath := fmt.Sprintf("%s/%s", nfs, relativePathToNfs)
	if !util.IsPathExists(logFilePath) {
		ctx.JSON(400, fmt.Sprintf("file not found: %s", logFilePath))
		return
	}

	offset32, _ := strconv.Atoi(strOffset)
	offset := int64(offset32)
	line, _ := strconv.Atoi(strLine)

	lines, fileSize, next, err := util.ReadLines(logFilePath, offset, line)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("file not read: %s", logFilePath))
		return
	}

	ctx.JSON(http.StatusOK, struct {
		Lines []string
		Size  int64
		Next  int64
	}{lines, fileSize, next})
}

func deleteStack(ctx *gin.Context) {
	id := ctx.Param("id")

	if err := db.DelStackById(id); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to delete stack by id: %s", id))
		return
	}

	if stack := db.GetStackById(id); stack != nil {
		chain := stack.ChainName

		chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chain))
		if err := os.RemoveAll(chainFolder); err != nil {
			log.Printf("Warning: failed to remove the folder %s: %v", chainFolder, err)
		}
	}

	ctx.JSON(http.StatusOK, "")
}

func doClassedPaginatedStacks(ctx *gin.Context) {
	strLimit := ctx.Query("limit")
	strOffset := ctx.Query("offset")
	class := ctx.Query("class")
	if len(strLimit) == 0 || len(strOffset) == 0 {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("absence of limit or offset"))
		//return
		strLimit = "10"
		strOffset = "0"
	}
	limit, err := strconv.Atoi(strLimit)
	offset, err := strconv.Atoi(strOffset)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("limit or offset must be number type"))
		return
	}

	stacks := db.GetClassedPaginatedStack(class, limit, offset)
	if stacks == nil {
		stacks = []db.Stack{}
	}
	ctx.JSON(http.StatusOK, stacks)
}

func doPaginatedStacks(ctx *gin.Context) {
	strLimit := ctx.Query("limit")
	strOffset := ctx.Query("offset")
	if len(strLimit) == 0 || len(strOffset) == 0 {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("absence of limit or offset"))
		//return
		strLimit = "10"
		strOffset = "0"
	}
	limit, err := strconv.Atoi(strLimit)
	offset, err := strconv.Atoi(strOffset)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("limit or offset must be number type"))
		return
	}

	stacks := db.GetPaginatedStack(limit, offset)
	log.Printf("seeked stacks: %v", stacks)
	if stacks == nil {
		ctx.JSON(http.StatusOK, "")
	} else {
		ctx.JSON(http.StatusOK, stacks)
	}

}

func doGenStackConfigForDag(ctx *gin.Context) {
	var config stack.DAG

	if err := ctx.BindJSON(&config); err != nil {
		//ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		ctx.JSON(http.StatusBadRequest, err)
		return
	}

	tmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), "dag")
	tmplPath, _ := util.ExpandTilde(tmpl)
	err := stack.ParseDAGTemplate(ctx.Writer, tmplPath, config)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func doGenStackConfig(ctx *gin.Context) {
	var poaConfig stack.POA
	consensus := ctx.GetHeader("Consensus-Template-Type")

	if err := ctx.BindJSON(&poaConfig); err != nil {
		//ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	poaConfig.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	//poaTmpl := "/workspace/chen/blockchains-aio/stack/poa.tmpl"
	poaTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	expendedPoaTmpl, _ := util.ExpandTilde(poaTmpl)
	err := stack.ParsePOATemplate(ctx.Writer, expendedPoaTmpl, poaConfig)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func doGenStackConfigForPos(ctx *gin.Context) {
	var config stack.POS
	//consensus := ctx.GetHeader("Consensus-Template-Type")

	if err := ctx.BindJSON(&config); err != nil {
		//ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	config.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	//poaTmpl := "/workspace/chen/blockchains-aio/stack/pos.tmpl"
	posTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), "pos")
	expendedPosTmpl, _ := util.ExpandTilde(posTmpl)
	log.Println("posTmpl path:", expendedPosTmpl)
	err := stack.ParsePOATemplate(ctx.Writer, expendedPosTmpl, config)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func doUpdateMixin(ctx *gin.Context) {
	newChainname := ctx.PostForm("chainname") // original chainname + nodeNum
	nodeNum := ctx.PostForm("nodeNum")
	consensus := "dag"
	stackIdStr := ctx.PostForm("stackId")

	if len(newChainname) == 0 || len(newChainname) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if _, err := strconv.Atoi(stackIdStr); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", stackIdStr))
		return
	}
	stackId, _ := strconv.Atoi(stackIdStr)

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), newChainname))
	if err := util.CreateAllDir(chainFolder); err != nil {
		ctx.JSON(400, fmt.Sprintf("Fail to create dir: %v", err))
		return
	}
	yaml := chainFolder + "/" + "compose.yml"
	//ctx.SaveUploadedFile(file, yaml)
	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Fail to write file: %v", err))
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerComposeDeployUp2("bootstrap", chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to run setup compose: %v", err))
		return
	}

	out, err = util.DockerComposeDeployUp2("mixin", chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to run booting compose: %v", err))
		return
	}

	if err := db.UpdateStack(newChainname, consensus, string(p), nodeNumber, stackId); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", newChainname, consensus, file.Filename, nodeNumber, err))
		return
	}

	ctx.JSON(200, out.String())
}

func doBootstrapMixin(ctx *gin.Context) {
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := "dag"

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	if err := util.CreateAllDir(chainFolder); err != nil {
		ctx.JSON(400, fmt.Sprintf("Fail to create dir: %v", err))
		return
	}
	yaml := chainFolder + "/" + conf.DefaultComposeFileName
	//ctx.SaveUploadedFile(file, yaml)
	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Fail to write file: %v", err))
		return
	}

	//log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	//out, err := util.DockerComposeDeployUp2("bootstrap", chainFolder)
	//if err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to run setup compose: %v", err))
	//	return
	//}

	out, err := util.DockerComposeDeployUp3(chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to run booting compose: %v", err))
		return
	}

	if err := db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, file.Filename, nodeNumber, err))
		return
	}

	ctx.JSON(200, out.String())
}

//func printMe(ctx *gin.Context) {
//	chainname := ctx.PostForm("chainname")
//	nodeNum := ctx.PostForm("nodeNum")
//	consensus := ctx.PostForm("consensus")
//
//	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)
//
//	file, _ := ctx.FormFile("file")
//	f, err := file.Open()
//	if err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
//		return
//	}
//	defer f.Close()
//
//}

func doDockStackDeployForEth(ctx *gin.Context) {

	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus")

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	//ctx.PostForm("ports")
	//ctx.PostForm("identity")
	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "poa"
	}

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()

	//if err := util.CreateAllDir(hardCodedPath); err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to create dir:%v", err))
	//	return
	//}

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	if err := stack.CopyDirs(chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	if err := stack.BuildPoaNodesDir(nodeNumber, chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	//yamlWithPath := hardCodedPath + "/" + chainname
	//yamlWithPath := conf.GetStackConfigFolder() + "/" + chainname
	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	yaml := chainFolder + "/" + chainname + ".yml"

	//log.Println(string(p))
	//ctx.JSON(200, string(p))
	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerStackDeploy(yaml, chainname, chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		return
	}

	ctx.JSON(200, string(out))
}

func doDockStackDeployForPosEth(ctx *gin.Context) {

	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := "pos"

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	// preparing for initial task:
	//    1. build nodes' directory: node0-n
	if err := stack.BuildPosNodesDir(nodeNumber, chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	//if err := stack.CopyDirs(chainname, consensus); err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
	//	return
	//}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	yaml := chainFolder + "/" + chainname + ".yml"

	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerStackDeploy(yaml, chainname, chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		return
	}

	ctx.JSON(200, string(out))
}
